package handling.mina;

import client.MapleClient;
import constants.ServerConstants;
import handling.MaplePacket;
import handling.SendPacketOpcode;
import java.util.concurrent.locks.Lock;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolEncoder;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tools.FileoutputUtil;
import tools.HexTool;
import tools.MapleAESOFB;
import tools.MapleCustomEncryption;
import tools.data.input.ByteArrayByteStream;
import tools.data.input.ByteInputStream;
import tools.data.input.GenericLittleEndianAccessor;

public class MaplePacketEncoder implements ProtocolEncoder
{
    private static final Logger log;
    
    public void encode(final IoSession session, final Object message, final ProtocolEncoderOutput out) throws Exception {
        final MapleClient client = (MapleClient)session.getAttribute(MapleClient.CLIENT_KEY);
        if (client != null) {
            final MapleAESOFB send_crypto = client.getSendCrypto();
            // message 是handle 处理后即将响应的数据,所以这里根据响应的类型,直接强转成MaplePacket了
            // MaplePacket对象类型是客户端能解析的对象,里面有个 runable 似乎是在客户端开启线程执行任务的
            final byte[] inputInitialPacket = ((MaplePacket)message).getBytes();
            if (ServerConstants.封包显示) {
                //获取二进制对象总长度
                final int packetLen = inputInitialPacket.length;
                //获取二进制数据的头部信息
                final int pHeader = this.readFirstShort(inputInitialPacket);
                final String pHeaderStr = Integer.toHexString(pHeader).toUpperCase();
                //根据头部长度,找出即将发往客户端的操作类型是什么 op,准备打印
                final String op = this.lookupRecv(pHeader);
                boolean show = true;
                final String s = op;
                switch (s) {
                    case "WARP_TO_MAP":
                    case "PING":
                    case "NPC_ACTION":
                    case "UPDATE_STATS":
                    case "MOVE_PLAYER":
                    case "SPAWN_NPC":
                    case "SPAWN_NPC_REQUEST_CONTROLLER":
                    case "REMOVE_NPC":
                    case "MOVE_LIFE":
                    case "MOVE_MONSTER":
                    case "MOVE_MONSTER_RESPONSE":
                    case "SPAWN_MONSTER":
                    case "SPAWN_MONSTER_CONTROL":
                    case "ANDROID_MOVE": {
                        show = false;
                        break;
                    }
                }
                final String Recv = "服务端发送 " + op + " [" + pHeaderStr + "] (" + packetLen + ")";
                if (packetLen <= 50000) { //数据包长度小于 50000 进行写日志打印,大于 5w 直接通过 log 输出
                    final String RecvTo = Recv + HexTool.toString(inputInitialPacket)  + HexTool.toStringFromAscii(inputInitialPacket);
                    if (show) {
                        MaplePacketEncoder.log.info("向客户端发送的数据包大于 50000 即将记录日志并输出");
                        FileoutputUtil.packetLog("log/服务端封包.log", RecvTo);
                        System.out.println(RecvTo);
                    }
                }
                else {
                    MaplePacketEncoder.log.info("向客户端发送的数据包大于 50000 输出位");
                    MaplePacketEncoder.log.info(HexTool.toString(new byte[] { inputInitialPacket[0], inputInitialPacket[1] }) + " ...");
                }
            }
            //新建一个数据包总长度的字节数组(此处插一个题外话,为什么是字节数组,因为一个字节是8 位,位就可以映射成 0101 高低电压进行输出)
            final byte[] unencrypted = new byte[inputInitialPacket.length];
            //将原数组进行拷贝
            System.arraycopy(inputInitialPacket, 0, unencrypted, 0, inputInitialPacket.length);
            //增加头部信息,并创建一个最终加密后可以返回的封包信息
            final byte[] ret = new byte[unencrypted.length + 4];
            //对同一个回话进行加锁,这样传递数据的时候,同一个会话只会顺序传递,因为只有上一个包传递完毕,才会开始传递下一个包
            final Lock mutex = client.getLock();
            mutex.lock();
            try {
                final byte[] header = send_crypto.getPacketHeader(unencrypted.length);
                MapleCustomEncryption.encryptData(unencrypted);
                //对即将返回客户端的封包进行加密,一定要做的步骤
                send_crypto.crypt(unencrypted);
                // 将头部信息写入响应封包中
                System.arraycopy(header, 0, ret, 0, 4);
            }
            finally {
                //当封包传递完毕后,释放所,然后就可以传递同一个会话的下一次封包了。这样可以保证封包是有绝对顺序的。能解决网络传输的顺序问题。因为走得网路一样
                mutex.unlock();
            }
            //将加密的数据包拷贝到新创建的字节数组中,然后进行返回
            System.arraycopy(unencrypted, 0, ret, 4, unencrypted.length);
            out.write(IoBuffer.wrap(ret));
        }
        else {
            // todo 不清楚client为 null 是哪个场景,猜测是登录?
            out.write(IoBuffer.wrap(((MaplePacket)message).getBytes()));
        }
    }
    
    public void dispose(final IoSession session) throws Exception {
    }
    
    private String lookupRecv(final int val) {
        for (final SendPacketOpcode op : SendPacketOpcode.values()) {
            if (op.getValue() == val) {
                return op.name();
            }
        }
        return "UNKNOWN";
    }
    
    private int readFirstShort(final byte[] arr) {
        return new GenericLittleEndianAccessor(new ByteArrayByteStream(arr)).readShort();
    }
    
    static {
        log = LoggerFactory.getLogger(MaplePacketEncoder.class);
    }
}
